/*******************************************************************************
 * Copyright (c) 2012 Alexandr Tsvetkov.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Contributors:
 *     Alexandr Tsvetkov - initial API and implementation
 * 
 * Project:
 *     TAO Library
 * 
 * File name:
 *     Zip.java
 *     
 * License agreement:
 *
 * 1. This code is published AS IS. Author is not responsible for any damage that can be
 *    caused by any application that uses this code.
 * 2. Author does not give a garantee, that this code is error free.
 * 3. This code can be used in NON-COMMERCIAL applications AS IS without any special
 *    permission from author.
 * 4. This code can be modified without any special permission from author IF AND ONLY IF
 *    this license agreement will remain unchanged.
 * 5. SPECIAL PERMISSION for this code usage in COMMERCIAL application SHOULD be obtained
 *    from author.
 ******************************************************************************/
package ua.at.tsvetkov.file;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import ua.at.tsvetkov.util.Log;

/**
 * @author A.Tsvetkov 2010 http://tsvetkov.at.ua mailto:al@ukr.net
 */
public class Zip {

	private final int	TIMEOUT			= 10000;
	private final int	BUFFER			= 8192;
	/**
	 * Zip file length
	 */
	public int			fileLength;
	/**
	 * Current loaded size
	 */
	public int			byteCount;
	/**
	 * Files in zip file
	 */
	public int			countFiles;
	/**
	 * Current extracting file
	 */
	public int			currentFileNumber;
	/**
	 * Download is begin. All data for dialog is prepare.
	 */
	public boolean		downloadBegin;
	/**
	 * Unzip is begin. All data for dialog is prepare.
	 */
	public boolean		unzipBegin;
	public boolean		downloadFinished;
	public boolean		unzipFinished;
	private boolean	breakDownload	= false;
	private boolean	isPaused			= false;

	public Zip() {
		breakDownload = false;
	}

	public void breakDownload() {
		breakDownload = true;
	}

	/**
	 * Loaded and unzip
	 * 
	 * @param url file URL
	 * @param zipFile zip file name
	 * @param tmpDir temporary dir for loaded zip file
	 * @param path target directory
	 * @param deleteTmpFile is need to delete loaded zip file after unzipping
	 * @throws MalformedURLException
	 * @throws IOException
	 */
	public void downloadAndUnzip(String url, String zipFile, String tmpDir, String path, boolean deleteTmpFile) throws MalformedURLException, IOException {
		downloadBegin = false;
		unzipBegin = false;
		downloadFinished = false;
		unzipFinished = false;
		download(url, tmpDir + zipFile);
		if (breakDownload)
			return;
		doUnzip(tmpDir + zipFile, path, true);
		if (deleteTmpFile) {
			File f = new File(tmpDir + zipFile);
			f.delete();
		}
	}

	/**
	 * Return file size
	 * 
	 * @return file size
	 */
	public int getFileLength() {
		return fileLength;
	}

	/**
	 * Return zip entries count
	 * 
	 * @param zipFileName
	 * @return zip entries count
	 * @throws IOException
	 */
	public int getZipEntries(String zipFileName) throws IOException, UnknownHostException {
		ZipFile zipFile = new ZipFile(zipFileName);
		return zipFile.size();
	}

	/**
	 * Downloads a remote zip file and stores it locally
	 * 
	 * @param url Remote URL of the file to download
	 * @param pathAndFileName Local path with file name where to store the file
	 * @throws IOException
	 * @throws MalformedURLException
	 * @throws Exception Read/write exception
	 */
	public void download(String url, String pathAndFileName) throws MalformedURLException, IOException, FileNotFoundException {
		HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
		conn.setDoInput(true);
		conn.setConnectTimeout(TIMEOUT); // timeout 10 secs
		conn.connect();
		// Define file length
		fileLength = conn.getContentLength();
		if (fileLength > 0) {
			downloadBegin = true;
		} else {
			downloadBegin = false;
			downloadFinished = true;
			throw new FileNotFoundException("File:" + url);
		}
		Log.v("Prepare for download zip file " + url + " size " + fileLength);
		InputStream input = conn.getInputStream();
		FileOutputStream fOut = new FileOutputStream(pathAndFileName);
		byteCount = 0;
		byte[] buffer = new byte[BUFFER];// new byte[4096];
		int bytesRead = 0;
		while (bytesRead != -1) {
			if (isPaused) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					Log.w(e);
				}
				continue;
			}
			bytesRead = input.read(buffer);
			if (bytesRead == -1)
				break;
			fOut.write(buffer, 0, bytesRead);
			byteCount += bytesRead;
			if (breakDownload)
				break;
		}
		fOut.flush();
		fOut.close();
		downloadFinished = true;
		Log.v("Loaded zip file from " + url + ", size " + byteCount);
	}

	/**
	 * Do unzip ZIP file to destination directory
	 * 
	 * @param inputZip zip file to extracting
	 * @param destinationDirectory distanation directory
	 * @param isRoot for external call always true
	 * @throws IOException
	 */
	public void doUnzip(String inputZip, String destinationDirectory, boolean isRoot) throws IOException {
		ArrayList<String> zipFiles = new ArrayList<String>();
		File sourceZipFile = new File(inputZip);
		File unzipDestinationDirectory = new File(destinationDirectory);
		unzipDestinationDirectory.mkdir();

		ZipFile zipFile;
		// Open Zip file for reading
		zipFile = new ZipFile(sourceZipFile, ZipFile.OPEN_READ);

		// Create an enumeration of the entries in the zip file
		Enumeration<?> zipFileEntries = zipFile.entries();
		countFiles = zipFile.size();
		currentFileNumber = 0;
		unzipBegin = true;
		Log.v("Prepare for unzip file " + inputZip);
		// Process each entry
		while (zipFileEntries.hasMoreElements() && !breakDownload) {

			currentFileNumber++;
			// grab a zip file entry
			ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();

			String currentEntry = entry.getName();

			File destFile = new File(unzipDestinationDirectory, currentEntry);
			// destFile = new File(unzipDestinationDirectory, destFile.getName());

			if (currentEntry.endsWith(".zip")) {
				zipFiles.add(destFile.getAbsolutePath());
			}

			// grab file's parent directory structure
			File destinationParent = destFile.getParentFile();

			// create the parent directory structure if needed
			destinationParent.mkdirs();

			// extract file if not a directory
			if (!entry.isDirectory()) {
				Log.v("Prepare " + currentEntry);
				BufferedInputStream is = new BufferedInputStream(zipFile.getInputStream(entry));
				int currentByte;
				// establish buffer for writing file
				byte data[] = new byte[BUFFER];

				// write the current file to disk
				FileOutputStream fos = new FileOutputStream(destFile);
				BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);

				// read and write until last byte is encountered
				while ((currentByte = is.read(data, 0, BUFFER)) != -1) {
					dest.write(data, 0, currentByte);
				}
				dest.flush();
				dest.close();
				Log.v("Success " + currentEntry);
				is.close();
			} else {
				File dir = new File(destinationDirectory + entry.getName());
				if (!dir.exists()) {
					dir.mkdirs();
				}
			}
		}
		zipFile.close();
		for (Iterator<String> iter = zipFiles.iterator(); iter.hasNext();) {
			String zipName = (String) iter.next();
			doUnzip(zipName, destinationDirectory + zipName.substring(0, zipName.lastIndexOf(".zip")), false);
		}
		if (isRoot) {
			Log.v("Successfull unziped files from " + inputZip + " to dir " + destinationDirectory);
			unzipFinished = true;
		}
	}

	public void pause() {
		isPaused = true;
	}

	public void resume() {
		isPaused = false;
	}

}
